package com.ushine.web.config.api;

import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import com.ushine.web.security.jwt.JwtProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.boot.SpringBootVersion;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.*;

/**
 * @Author yls
 * @Description 集成swagger3 文档
 * 页面访问地址为：http://localhost:8621/swagger-ui.html
 * @Date 2020/4/5 9:15
 **/
@Configuration
@EnableKnife4j
@Slf4j
public class SwaggerConfig implements WebMvcConfigurer {

    @Resource
    JwtProperties jwtProperties;

    private final SwaggerProperties swaggerProperties;

    public SwaggerConfig(SwaggerProperties swaggerProperties) {
        this.swaggerProperties = swaggerProperties;
    }


    /*******************************************************默认api-start*************************************************************/
    /**
     * 功能描述:
     * 〈swagger2 接口文档管理器〉
     *
     * @return : springfox.documentation.spring.web.plugins.Docket
     * @author : yls
     * @date : 2020/07/12 10:48
     */
    @Bean
    public Docket defaultApi() {
        String pathMapping = "/**";
        String groupName = "default";
        return getDocket(pathMapping, groupName);
    }

    /**
     * 功能描述:
     * 〈 基础模块 〉
     *
     * @return : springfox.documentation.spring.web.plugins.Docket
     * @author : yls
     * @date : 2022/2/10 15:24
     */
    @Bean
    public Docket baseModelApi() {
        String pathMapping = "/system/**";
        String groupName = "基础模块";
        return getDocket(pathMapping, groupName);
    }


    /**
     * 功能描述:
     * 〈 日志模块 〉
     *
     * @return : springfox.documentation.spring.web.plugins.Docket
     * @author : yls
     * @date : 2022/2/10 15:26
     */
    @Bean
    public Docket logModelApi() {
        String pathMapping = "/log/**";
        String groupName = "日志模块";
        return getDocket(pathMapping, groupName);
    }

    /**
     * 功能描述:
     * 〈 组件模块 〉
     *
     * @param
     * @return : springfox.documentation.spring.web.plugins.Docket
     * @author : yls
     * @date : 2022/2/10 15:30
     */
    @Bean
    public Docket componentModelApi() {
        String pathMapping = "/component/**";
        String groupName = "组件模块";
        return getDocket(pathMapping, groupName);
    }

    /**
     * 功能描述:
     * 〈 分组api信息
     * <p>
     * * 配置了Swagger的Docket的bean实例：Swagger3 需要配置成 DocumentationType.OAS_30
     * *  enable()：是否启动Swagger，如果为False，则Swagger不能在浏览器中访问
     * *  apiInfo()：配置api文档元信息
     * *  select()：选择哪些接口作为swagger的doc发布，一般和apis()一起使用
     * *  apis(RequestHandlerSelectors.XXX)：配置要扫描的方式
     * *      RequestHandlerSelectors.basePackage()：指定要扫描的包
     * *      RequestHandlerSelectors.any()：扫描全部
     * *      RequestHandlerSelectors.none()：不扫描
     * *      RequestHandlerSelectors.withClassAnnotation()：扫描类上的注解，参数是注解的反射对象
     * *      RequestHandlerSelectors.withMethodAnnotation()：扫描方法上的注解，参数是注解的反射对象
     * *      例如：RequestHandlerSelectors.withClassAnnotation(ApiOperation.class)
     * *  paths()：过滤什么路径，一般跟在select()之后，根据请求路径定义当前Docket需要包含控制器的哪些方法。
     * *      PathSelectors.none()：不扫描
     * *      PathSelectors.any()：扫描所有请求路径
     * *      PathSelectors.ant("/*")：匹配Ant样式的路径模式
     * *      PathSelectors.regex("/cat/.*")：匹配正则指定的正则表达式路径
     * *  pathMapping()：默认请求都是以 / 根路径开始.如果我们的应用不是部署在根路径，
     * *                 比如以/platform 部署，则可以通过一下方式设置请求的统一前缀。最终调用接口后会和paths拼接在一起.
     * *                 如接口为：/v1/api/list，那么swagger-ui页面则显示/platform/v1/api/list
     * *
     * *  build：工厂模式
     * *  注意：Docket.select().xxx.build()必须一起使用
     * *        select() 返回的是一个ApiSelectorBuilder对象，而我们需要的却是Docket对象，
     * *        因此，ApiSelectorBuilder类中提供了一个方法 --> build() 方法返回的是一个Docket对象。
     * <p>
     * 〉
     *
     * @param pathMapping 基础路径映射
     * @param groupName   分组名称
     * @return : springfox.documentation.spring.web.plugins.Docket
     * @author : yls
     * @date : 2022/2/10 15:25
     */
    private Docket getDocket(String pathMapping, String groupName) {
        return new Docket(DocumentationType.OAS_30)
                .pathMapping("/")
                .groupName(groupName)
                // 定义是否开启swagger，false为关闭，可以通过变量控制
                .enable(swaggerProperties.getEnable())
                // 将api的元信息设置为包含在json ResourceListing响应中。
                .apiInfo(defaultApiInfo())
                // 接口调试地址
                .host(swaggerProperties.getTryHost())
                // 选择哪些接口作为swagger的doc发布
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.ant(pathMapping))
                .build()
                // 支持的通讯协议集合
                .protocols(newHashSet("https", "http"))
                // 授权信息设置，必要的header token等认证信息
                .securitySchemes(securitySchemes())
                // 授权信息全局应用
                .securityContexts(securityContexts());
    }

    /**
     * 功能描述:
     * 〈apiInfo对象主要是设置我们api文档的标题，描述，访问的地址，创建者等信息〉
     *
     * @param
     * @return : springfox.documentation.service.ApiInfo
     * @author : yls
     * @date : 2020/11/09 20:54
     */
    @SuppressWarnings("deprecation")
    private ApiInfo defaultApiInfo() {
        return new ApiInfoBuilder()
                .title(swaggerProperties.getApplicationName() + " Api Doc")
                .description(swaggerProperties.getApplicationDescription())
                .contact(new Contact("yls", null, "24674874@qq.com"))
                .version("Application Version: " + swaggerProperties.getApplicationVersion() + ", Spring Boot Version: " + SpringBootVersion.getVersion())
                .build();
    }
    /*******************************************************默认api-end*************************************************************/


    /**
     * 功能描述:
     * 〈安全模式，这里指定token通过Authorization头请求头传递〉
     *
     * @return : java.util.List<springfox.documentation.service.SecurityScheme>
     * @author : yls
     * @date : 2022/2/10 15:31
     */
    private List<SecurityScheme> securitySchemes() {
        ApiKey apiKey = new ApiKey(jwtProperties.getHeader(), jwtProperties.getHeader(), "header");
        return Collections.singletonList(apiKey);
    }

    /**
     * 授权信息全局应用
     */
    private List<SecurityContext> securityContexts() {
        return Collections.singletonList(
                SecurityContext.builder()
                        .securityReferences(Collections.singletonList(new SecurityReference(jwtProperties.getHeader(), new AuthorizationScope[]{new AuthorizationScope("global", "")})))
                        .build()
        );
    }

    @SafeVarargs
    private final <T> Set<T> newHashSet(T... ts) {
        if (ts.length > 0) {
            return new LinkedHashSet<>(Arrays.asList(ts));
        }
        return new LinkedHashSet<>();
    }


    /**
     * 通用拦截器排除swagger设置，所有拦截器都会自动加swagger相关的资源排除信息
     */
    @SuppressWarnings("unchecked")
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        try {
            Field registrationsField = FieldUtils.getField(InterceptorRegistry.class, "registrations", true);
            List<InterceptorRegistration> registrations = (List<InterceptorRegistration>) ReflectionUtils.getField(registrationsField, registry);
            if (registrations != null) {
                for (InterceptorRegistration interceptorRegistration : registrations) {
                    interceptorRegistration
                            .excludePathPatterns("/swagger**/**")
                            .excludePathPatterns("/webjars/**")
                            .excludePathPatterns("/v3/**")
                            .excludePathPatterns("/doc.html");
                }
            }
        } catch (Exception e) {
            log.error("swagger-addInterceptors=》exception:", e);
        }
    }

}